#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>

typedef struct camera_format {
    unsigned char description[32]; //字符串描述信息
    unsigned int pixelformat; //像素格式
} cam_fmt;

static cam_fmt cam_fmts[2];

int video_width = 640, video_height= 360; //摄像头采集数据的分辨率

int main(int argc,char *argv[])
{
    int ret,i;
    int fd;
    unsigned short *base;
    unsigned short *start;
    int min_w, min_h;
    int j;

    /*步骤一，打开视频设备*/
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        printf("file open error\n");
        return-1;
    }
    /*步骤二，查询设备能力*/
    //查询设备的基本信息
    struct v4l2_capability cap; //定义一个v4l2_capability结构体的变量cap
    //使用ioctl函数发送VIDIOC_QUERYCAP命令来获取视频设备的基本信息，并将结果保存到cap变量中
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
    {
        perror("VIDIOC_QUERYCAP");
        return-1;
    }
    //查看支持的图像格式、分辨率、帧率
    struct v4l2_fmtdesc fmtdesc = {0};//定义支持的像素格式结构体
    struct v4l2_frmsizeenum frmsize = {0};//定义支持的分辨率结构体
    struct v4l2_frmivalenum frmival = {0};//定义支持的帧率结构体
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置视频采集类型为V4L2_BUF_TYPE_VIDEO_CAPTURE
    fmtdesc.index = 0;
    while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))//获取支持的像素格式
    {
        strcpy(cam_fmts[fmtdesc.index].description, fmtdesc.description);
        cam_fmts[fmtdesc.index].pixelformat = fmtdesc.pixelformat;
        fmtdesc.index++;
    }
    frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for(i=0;i<fmtdesc.index;i++)//枚举每一种像素格式
    {
        printf("description:%s\npixelformat:0x%x\n",
        cam_fmts[i].description,cam_fmts[i].pixelformat );
        frmsize.index = 0;
        frmsize.pixel_format = cam_fmts[i].pixelformat;
        frmival.pixel_format = cam_fmts[i].pixelformat;
        // 2.枚举出摄像头所支持的所有视频采集分辨率
        while (0 == ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize))
        {
            printf("size<%d*%d> ",frmsize.discrete.width,frmsize.discrete.height);
            frmsize.index++;
            frmival.index = 0;
            frmival.width = frmsize.discrete.width;
            frmival.height = frmsize.discrete.height;
            // 3.获取摄像头视频采集帧率
            while (0 == ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival))
            {
                printf("<%dfps>",
                frmival.discrete.denominator/frmival.discrete.numerator);
                frmival.index++;
            }
            printf("\n");
        }
        printf("\n");
    }
    /*步骤三，设置采集参数，视频帧宽度、高度、格式、视频帧率等信息*/
    struct v4l2_format fmt = {0};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type类型
    fmt.fmt.pix.width = video_width; //设置视频帧宽度
    fmt.fmt.pix.height = video_height;//设置视频帧高度
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置像素格式
    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0)
    {
        printf("ioctl error: VIDIOC_S_FMT\n");
        return-1;
    }
    struct v4l2_streamparm streamparm = {0};
    streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(fd, VIDIOC_G_PARM, &streamparm);
    if (V4L2_CAP_TIMEPERFRAME& streamparm.parm.capture.capability)
    {
        streamparm.parm.capture.timeperframe.numerator = 1;
        streamparm.parm.capture.timeperframe.denominator = 30;//30fps
        if (0 > ioctl(fd, VIDIOC_S_PARM, &streamparm))
        {
            printf("ioctl error: VIDIOC_S_PARM");
            return-1;
        }
    }
    /*步骤四，请求帧缓冲*/
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = 4;//缓存数量
    reqbuffer.memory = V4L2_MEMORY_MMAP;//映射方式
    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
    if (ret < 0)
    {
        printf("Request Queue space failed \n");
        return-1;
    }
    /*步骤五，映射帧缓冲*/
    struct v4l2_buffer mapbuffer;
    unsigned char *mptr[4];
    unsigned int size[4];//存储大小，方便释放
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for (i = 0; i < 4; i++)
    {
        mapbuffer.index = i;
        ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);
        if (ret < 0)
        {
            printf("Kernel space queue failed\n");
            return-1;
        }
        mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE,
        MAP_SHARED, fd, mapbuffer.m.offset);
        size[i] = mapbuffer.length;
        //使用完毕,入队
        ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
        if (ret < 0)
        {
            printf("ioctl error: VIDIOC_QBUF \n");
            return-1;
        }
    }
    /*步骤六，开启视频采集*/
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret < 0)
    {
        printf("ioctl error: VIDIOC_STREAMON \n");
        return-1;
    }
    //步骤七，读取数据、对数据进行处理
    unsigned char rgbdata[video_width*video_height*3];
    while (1)
    {
        struct v4l2_buffer readbuffer;
        //出队列
        readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
        if (ret < 0)
        {
            printf("ioctl error:VIDIOC_DQBUF \n");
        }
        // //显示在LCD上
        // yuyv_to_rgb(mptr[readbuffer.index], rgbdata, video_width, video_height);
        // lcd_show_rgb(rgbdata, video_width, video_height);
        //通知内核已经使用完毕，入队列
        ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
        if (ret < 0)
        {
            printf("ioctl error:VIDIOC_QBUF \n");
        }
    }
    //步骤八，停止视频采集和释放资源
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    if (ret < 0)
    {
        printf("ioctl error:VIDIOC_STREAMOFF \n");
        return-1;
    }
    //释放映射空间
    for (i = 0; i < 4; i++)
    {
        munmap(mptr[i], size[i]);
    }
    close(fd);
    return 0;
}

